-- Copyright (c) 2024 Huawei Technologies Co., Ltd.
-- openUBMC is licensed under Mulan PSL v2.
-- You can use this software according to the terms and conditions of the Mulan PSL v2.
-- You may obtain a copy of Mulan PSL v2 at: http://license.coscl.org.cn/MulanPSL2
-- THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
-- EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
-- MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
-- See the Mulan PSL v2 for more details.

-- Description: SMBus命令字接口
local class = require 'mc.class'
local bs = require "mc.bitstring"
local log = require 'mc.logging'
local MCU_ENUMS = require 'mcu.enum.mcu_enums'
local smbus = require 'protocol.smbus'
local skynet = require 'skynet'
local upgrade_interface = require 'mcu.upgrade.upgrade_interface'

local SMBUS_REQ_COMMAND_CODE = 0x20
local SMBUS_RSP_COMMAND_CODE = 0x21

local STD_SMBUS_LAST_FRAME = 0x80 --  最后一帧请求数据
local HEADER_LEN = 12
local MCU_SMBUS_FRAME_MAXSIZE = 32
local MCU_STATUS_RESP_DATA_LENGTH = 0x0E
local MCU_VENDORID_RESP_DATA_LENGTH = 0x0E
local MCU_VERSION_RESP_DATA_LENGTH = 0x0F
local MCU_SMBUS_PAYLOAD_MAXSIZE = MCU_SMBUS_FRAME_MAXSIZE - HEADER_LEN

local OPCODE_QUERY_VENDORID = 0x06 -- 查询MCU的Vendor ID
local MCU_UPGRADE_OPCODE_UPLOAD_FILE = 0x18 -- 传输升级文件
local OPCODE_UPGRADE = 0x19 -- 升级控制命令，需要带参数,参数: 1:启动升级 3:触发升级生效
local OPCODE_QUERY_VERSION = 0x05 -- 查询MCU版本号
local OPCODE_QUERY_STATUS = 0x1A  -- 查询升级状态 

local bs_req = bs.new([[<<
    lun:8,
    arg:8,
    opcode:16,
    offset:32,
    data_length:32,
    data/string
>>]])

local smbus_interface = class(upgrade_interface)

function smbus_interface:blkwrite(req)
    local ok, err = pcall(smbus.chip_blkwrite, self.chip, self.address, SMBUS_REQ_COMMAND_CODE, req)

    if not ok then
        log:debug("send write cmd to chip fail, err: %s", err)
        return false
    end

    return true
end

function smbus_interface:blkread(req, len)
    log:info("chip=%s, address=%s", self.chip, self.address)
    local ok, err = pcall(smbus.chip_blkwrite, self.chip, self.address, SMBUS_REQ_COMMAND_CODE, req)
    if not ok then
        log:debug("send write cmd to chip fail, err: %s", err)
        return false, "send write cmd to chip fail"
    end

    local ok2, rsp = pcall(function ()
        local value = smbus.chip_blkread(self.chip, self.address, SMBUS_RSP_COMMAND_CODE, len)
        if not value then
            error('get value is nil')
        end
        local error_code = string.sub(value, 1):byte()
        if error_code ~= 0 then
            error('get code is error')
        end
        return value
    end)

    return ok2, rsp
end

function smbus_interface:blkwrite_read(req, len)
    log:info("chip=%s, address=%s", self.chip, self.address)
    local ok, rsp = pcall(smbus.chip_blkwrite_read, self.chip, self.address,
        SMBUS_REQ_COMMAND_CODE, req, SMBUS_RSP_COMMAND_CODE, len)

    return ok, rsp
end

--- @function 获取厂商id
--- @return any 厂商id
function smbus_interface:get_vendor_id(...)
    local req = bs_req:pack({lun = STD_SMBUS_LAST_FRAME,
                            arg = 0,
                            opcode = OPCODE_QUERY_VENDORID,
                            offset = 0,
                            data_length = MCU_VENDORID_RESP_DATA_LENGTH - HEADER_LEN,
                            data = ""})
    local ok, rsp
    for i = 1, 5 do
        ok, rsp = self:blkread(req, MCU_VENDORID_RESP_DATA_LENGTH)
        skynet.sleep(100)
        if ok then
            break
        end
        log:notice("try to get mcu vendor id, retry time = %s, rsp is %s", i, rsp)
    end
    if not ok or not rsp then
        log:error("get mcu vendor id fail, rsp is %s", rsp)
        return nil
    end
    -- 返回值的13和14位组成厂商id
    local ven_id1 = string.sub(rsp, 14):byte()
    local ven_id2 = string.sub(rsp, 13):byte()
    local vendor_id = (ven_id1 << 8) | ven_id2
    return vendor_id
end

--[[发送后MCU返回升级信息,返回值: 
    0: 空闲状态 
    1:正在升级主app分区 
    2:当前状态不支持升级，有可能主app和备份app分区在同步 
    3:升级失败
--]]
--- @function 获取mcu升级状态
--- @return any 升级文件名
function smbus_interface:query_upgrade_status()
    local req = bs_req:pack({lun = STD_SMBUS_LAST_FRAME,
                            arg = 0,
                            opcode = OPCODE_QUERY_STATUS,
                            offset = 0,
                            data_length = MCU_VENDORID_RESP_DATA_LENGTH - HEADER_LEN,
                            data = ""})

    local ok, rsp = self:blkwrite_read(req, MCU_STATUS_RESP_DATA_LENGTH)
    if not ok then
        if rsp.name == 'kepler.hwproxy.RequestTimeout' or rsp.name == 'org.freedesktop.DBus.Error.NoReply' or
            rsp.name == 'ChipRequestLimitExceeded' then
            return MCU_ENUMS.MCU_UPGRADE_STATUS.INVALID_UPGRADE_STATUS
        end
        log:info("query_mcu_upgrade_status fail")
        return nil
    end
    -- 返回值的13为状态 14位升级进度
    local status = string.sub(rsp, 13):byte()
    log:info("upgrade state is %s", status)
    return status
end

--- @function 获取版本信息
--- @return any 主版本
--- @return any 备份版本
function smbus_interface:get_version(...)
    local req = bs_req:pack({lun = STD_SMBUS_LAST_FRAME,
                            arg = 0,
                            opcode = OPCODE_QUERY_VERSION,
                            offset = 0,
                            data_length = MCU_VERSION_RESP_DATA_LENGTH - HEADER_LEN,
                            data = ""})

    local ok, rsp = self:blkread(req, MCU_VERSION_RESP_DATA_LENGTH)
    if not ok or not rsp then
        log:info("try to get_mcu_version fail, ")
        return nil
    end
    -- 返回值的13为major版本,14为minor版本
    local major_version = string.sub(rsp, 13):byte()
    local minor_version = string.sub(rsp, 14):byte()
    local revision = string.sub(rsp, 15):byte()
    return major_version, minor_version, revision
end

--- @function 发送固件数据
--- @param data string 固件数据
--- @return boolean true/false
function smbus_interface:send_upgrade_file(sub_comp, data, send_params_list, process)
    return self:send_mcu_req(MCU_UPGRADE_OPCODE_UPLOAD_FILE, data, process)
end

--- @function 发送启动升级命令
--- @return number RET_OK和RET_ERR
function smbus_interface:start_upgrade()
    return self:send_mcu_req(OPCODE_UPGRADE, MCU_ENUMS.MCU_UPGRADE_CMD.INITATE)
end

--- @function 发送命令给MCU使升级的固件生效，MCU收到命令后会重启软件
--- @return number RET_OK和RET_ERR
function smbus_interface:take_upgrade_effect()
    return self:send_mcu_req(OPCODE_UPGRADE, MCU_ENUMS.MCU_UPGRADE_CMD.TAKEEFFECT)
end

--- @function 向mcu发送带请求体的消息
--- @param type number 请求类型
--- @param data any 请求数据
--- @return any 发送结果
function smbus_interface:send_mcu_req(type, data, process_callback)
    local length = #tostring(data)
    local lun = 0
    local offset = 0
    local written_len = 0
    local write_data_batch = {}
    local str, req
    while offset < length do
        -- 截取20个字节
        str = string.sub(data, offset + 1, offset + MCU_SMBUS_PAYLOAD_MAXSIZE)
        -- 最后不到20个字节的情况
        if offset + MCU_SMBUS_PAYLOAD_MAXSIZE >= length then
            lun = lun | STD_SMBUS_LAST_FRAME
            written_len = length - offset
        else
            lun = lun & ~STD_SMBUS_LAST_FRAME
            written_len = MCU_SMBUS_PAYLOAD_MAXSIZE
        end
        req = bs_req:pack({
            lun = lun,
            arg = 0,
            opcode = type,
            offset = offset,
            data_length = written_len,
            data = str
        })

        -- 每发送2048字节(2KB)，延时1秒
        if (offset // 2048) > (offset - written_len) // 2048 then
            local ok, err = pcall(smbus.chip_batch_write, self.chip, self.address, write_data_batch)
            if not ok then
                log:error('smbus send data to mcu failed, err: %s', err)
                return false
            end
            log:info('smbus wait 1s to transfer MCU file.')
            skynet.sleep(100) -- 延时100 * 10ms
            write_data_batch = {}
        end

        write_data_batch[#write_data_batch + 1] = {SMBUS_REQ_COMMAND_CODE, req}
        offset = offset + written_len
        if process_callback then
            process_callback(offset / length)
        end
    end
    if next(write_data_batch) then
        local ok, err = pcall(smbus.chip_batch_write, self.chip, self.address, write_data_batch)
        if not ok then
            log:error('smbus send data to mcu failed, err: %s', err)
            return false
        end
        write_data_batch = {}
    end

    return true
end

function smbus_interface:init_upgrade(...)
    -- smbus协议升级不需要初始化升级通道,直接返回true
    return true
end

--- @function 查询生效模式
--- @return any 生效模式
--- 查询失败时默认为直接生效，升级结束后直接执行生效动作
function smbus_interface:query_active_mode()
    return MCU_ENUMS.ACTIVE_IMMEDIATELY
end

--- @function 执行生效动作
--- @return any 生效模式
--- MCU生效流程归一，组件可能未实现此命令字，默认无生效动作
function smbus_interface:exec_valid_action()
end

return smbus_interface
